/******************************************************
*  Copyright(c) pages.ilovezxl.top
*  FileName : log.c
*  Author : rivulet.dave
*  Description : LOG功能模块
*  Data : 2019-04-15
******************************************************/

/*********************************************************************
 * INCLUDES
 */

#include <stdarg.h>

#include "stdrope.h"
#include "sallib.h"
#include "salagent.h"

#include "link.h"
#include "ringbuf.h"
#include "cli.h"
#include "mem.h"
#include "ostime.h"
#include "log.h"

#if (SAL_LOG == _ENABLE)

/*********************************************************************
 * TYPES
 */

typedef struct _log_context
{
    uint8_t ucLevel;
    uint8_t aucReserved[3];
    SLINK_S stOutputList;
    RING_BUF_S stRingBuffer;
} LOG_CONTEXT_S;

typedef struct _log_output
{
    SLINK_NODE_S stQueNode;
    logoutput_callback_t pfCallback;
} LOG_OUTPUT_S;

/*********************************************************************
 * MACROS
 */

#define LOG_PREFIX_MAX_LEN      128

/*********************************************************************
 * FUNCTIONS DECLARE
 */

static void LOG_CLICallBack(char *pArg);
static void LOG_AddPrefix(char *pcBuffer, char *pcModuleName, uint8_t ucLevel, char *pcFileName, uint32_t uiLine);
static void LOG_DbgPrint(const LOGBUF_NODE_S *pstLogBuf);

/*********************************************************************
 *  VARIABLES
 */

static LOG_CONTEXT_S s_stLogContext;
static CLI_INFO_S s_astLogCliTbl[] =
{
    { "log", "log function control.", LOG_CLICallBack },
};

/*********************************************************************
 * FUNCTIONS
 */

/**
 *   @Description: LOG功能初始化
 *   @param  : ucLevel - 入参， LOG级别
 *   @return : NA
 */
void LOG_Init(uint8_t ucLevel)
{
    if (ucLevel > LOG_LEVEL_INVALID)
    {
        return;
    }
    
    s_stLogContext.ucLevel = ucLevel;
    
    SLINK_Init(&s_stLogContext.stOutputList);
    RB_RingBufInit(&s_stLogContext.stRingBuffer, LOG_RING_QUEUE_SIZE, sizeof(LOGBUF_NODE_S));

    CLI_Register(s_astLogCliTbl, _ARRAY_SIZE(s_astLogCliTbl));
    LOG_OutputRegister(LOG_DbgPrint);
}

/**
 *   @Description: 日志输出
 *   @param : pcModuleName - 输出日志的模块名称
 *   @param : ucLevel - 日志级别
 *   @param : uiKey - 区分不同的缓存区
 *   @param : pcFileName - 文件名
 *   @param : uiLine - 所在行号
 *   @param : format_msg
 *   @return: NA
 */
void LOG_Output(char *pcModuleName, uint8_t ucLevel, uint32_t uiKey, char *pcFileName, uint32_t uiLine, char *pcFormatMsg, ...)
{
    char acTemp[LOG_ONELINE_MAX_LEN];
    LOGBUF_NODE_S stLogBuffer;
    int32_t iRet;
    va_list stList;

	if (pcModuleName == NULL)
	{
		return;
	}

    if (ucLevel < s_stLogContext.ucLevel)
    {
        return;
    }

    STR_ARRAY_INIT(acTemp, LOG_ONELINE_MAX_LEN);
    STR_ARRAY_INIT(stLogBuffer.acBuffer, LOG_ONELINE_MAX_LEN);
    
    va_start(stList, pcFormatMsg);
    LOG_AddPrefix(stLogBuffer.acBuffer, pcModuleName, ucLevel, pcFileName, uiLine);
    iRet = vsnprintf(acTemp, sizeof(acTemp), pcFormatMsg, stList);
    if (0 > iRet)
    {
        return;
    }

    if (LOG_ONELINE_MAX_LEN - 1 < strlen(stLogBuffer.acBuffer) + strlen(acTemp))
    {
        return;
    }

    stLogBuffer.ucLevel = ucLevel;
    strcat(stLogBuffer.acBuffer, acTemp);
    if (_OK != RB_RingBufPush(&s_stLogContext.stRingBuffer, &stLogBuffer))
    {
        printf("log buffer full.\n");
    }
    va_end(stList);

    return;
}

/**
 *   @Description: 以16进制输出数据
 *   @param : pcModuleName - 输出日志的模块名称
 *   @param : uiKey - 区分不同的缓存区
 *   @param : pcTitle - 输出前的标题
 *   @param : puiMsg - 输出的数据
 *   @param : uiMsgLen - 输出的数据的长度
 *   @param : pcFileName - 输出日志的文件名
 *   @param : uiLine - 输出日志的行号
 *   @return: NA
 */
void LOG_HexDumpOutput(char *pcModuleName, uint32_t uiKey, char *pcTitle, const uint8_t *pucMsg, uint32_t uiMsgLen, char *pcFileName, uint32_t uiLine)
{
    uint8_t line = 0;
    char acHexBuffer[LOG_ONELINE_MAX_LEN] = { 0 };
    
    if (LOG_LEVEL_DEBUG < s_stLogContext.ucLevel)
    {
        return;
    }

    if (LOG_ONELINE_MAX_LEN < uiMsgLen)
    {
        return;
    }
    
    LOG_Output(pcModuleName, LOG_LEVEL_DEBUG, uiKey, pcFileName, uiLine, "%s(%d):\n", pcTitle, uiMsgLen);
    
    for (line = 0; line < uiMsgLen / LOG_HEX_ONELINE_SIZE; line++)
    {
        for (uint8_t i = 0; i < LOG_HEX_ONELINE_SIZE; i++)
        {
            (void)snprintf(acHexBuffer + 3 * i, sizeof(acHexBuffer) - 3 * i, "%02X ", pucMsg[line * LOG_HEX_ONELINE_SIZE + i]);
        }
        strcat(acHexBuffer, "\n");
        LOG_Output(pcModuleName, LOG_LEVEL_RAW, uiKey, pcFileName, uiLine, "%s", acHexBuffer);
    }

    if (0 < uiMsgLen % LOG_HEX_ONELINE_SIZE)
    {
        for (uint8_t i = 0; i < uiMsgLen % LOG_HEX_ONELINE_SIZE; i++)
        {
            (void)snprintf(acHexBuffer + 3 * i, sizeof(acHexBuffer) - 3 * i, "%02X ", pucMsg[line * LOG_HEX_ONELINE_SIZE + i]);
        }
        strcat(acHexBuffer, "\n");
        LOG_Output(pcModuleName, LOG_LEVEL_RAW, uiKey, pcFileName, uiLine, "%s", acHexBuffer);
    }
}

/**
 *   @Description: 注册LOG输出的方式
 *   @param : pfLogOutput - 入参，log输出的回调函数
 *   @return: 错误信息
 */
int32_t LOG_OutputRegister(logoutput_callback_t pfLogOutput)
{
    bool bIsRepeat = false;
    SLINK_NODE_S *pstTempNode;
    LOG_OUTPUT_S *pstLogOutputNode = NULL;

    if (NULL == pfLogOutput)
    {
        return _ERR;
    }

    // 检查是否有重复注册
    pstTempNode = s_stLogContext.stOutputList.pstHead;
    while (pstTempNode)
    {
        LOG_OUTPUT_S *pstTempCli = (LOG_OUTPUT_S *)pstTempNode;
        if (pfLogOutput == pstTempCli->pfCallback)
        {
            bIsRepeat = true;
            break;
        }

        pstTempNode = pstTempNode->pNext;
    }

    if (false == bIsRepeat)
    {
        pstLogOutputNode = (LOG_OUTPUT_S *)osMemAlloc(sizeof(LOG_OUTPUT_S));
        if (NULL != pstLogOutputNode)
        {
            pstLogOutputNode->pfCallback = pfLogOutput;
            SLINK_Append(&s_stLogContext.stOutputList, (SLINK_NODE_S *)pstLogOutputNode);
        }
    }
    
    return _OK;
}

/**
 *   @Description: 取消LOG输出
 *   @param : pfLogOutput - 入参，log输出的回调函数，与注册时的一致
 *   @return: 错误信息
 */
int32_t LOG_OutputUnregister(logoutput_callback_t pfLogOutput)
{
    SLINK_NODE_S *pstTempNode;

    if (NULL == pfLogOutput)
    {
        return _ERR;
    }

    // 查找注册节点
    pstTempNode = s_stLogContext.stOutputList.pstHead;
    while (pstTempNode)
    {
        LOG_OUTPUT_S *pstTempCli = (LOG_OUTPUT_S *)pstTempNode;
        if (pfLogOutput == pstTempCli->pfCallback)
        {
            break;
        }

        pstTempNode = pstTempNode->pNext;
    }

    if (NULL != pstTempNode)
    {
        SLINK_DeleteNode(&s_stLogContext.stOutputList, pstTempNode);
        osMemFree((void **)&pstTempNode);
    }
    
    return _OK;
}

/**
 *   @Description: 将缓存中的日志写入到文件或输出，调用注册的回调函数
 *   @param : uiLogNum - 入参，一次输出的日志数据，0代表缓存中的所有日志
 *   @return: NA
 */
void LOG_Flush(uint32_t uiLogNum)
{
    uint32_t uiCnt = 0;
    LOGBUF_NODE_S stLogBuf;

    while (_OK == RB_RingBufPop(&s_stLogContext.stRingBuffer, &stLogBuf))
    {
        // 根据output list输出日志
        SLINK_NODE_S *pstTempNode = s_stLogContext.stOutputList.pstHead;
        while (pstTempNode)
        {
            LOG_OUTPUT_S *pstLogOutputNode = (LOG_OUTPUT_S *)pstTempNode;
            if (pstLogOutputNode->pfCallback)
            {
                pstLogOutputNode->pfCallback(&stLogBuf);
            }

            pstTempNode = pstTempNode->pNext;
        }
        
        if (0 != uiLogNum && ++uiCnt >= uiLogNum)
        {
            break;
        }
    }
}

/**
 *   @Description: 添加LOG前缀
 *   @param : *pcBuffer - LOG输出缓存
 *   @param : pcModuleName - 输出日志的模块名称
 *   @param : uiLevel - LOG级别，参见LOG_LEVEL_E.
 *   @param : *pcFileName - 文件名
 *   @param : uiLine - 行号
 *   @return: NA
 */
static void LOG_AddPrefix(char *pcBuffer, char *pcModuleName, uint8_t ucLevel, char *pcFileName, uint32_t uiLine)
{
    const char *pLevelTag[] = { "DBG", "INF", "WRN", "ERR", "" };

    if ((NULL == pcModuleName) || (NULL == pcFileName))
    {
        return;
    }

    /* RAW不加前缀，原样输出 */
    if ((LOG_LEVEL_RAW != ucLevel) && (ucLevel < LOG_LEVEL_INVALID))
    {
        OS_DATE_S stCurData = { 0 };

        osDateGet(&stCurData);
        (void)sprintf( pcBuffer,
                       "[%04u-%02u-%02u %02u:%02u:%02u.%03u %7s %3s %10.10s:%-4u]",
                       stCurData.usYear, stCurData.ucMonth, stCurData.ucDay,
                       stCurData.ucHour, stCurData.ucMinute, stCurData.ucSecond, (unsigned int)stCurData.uiMilliSecond,
                       pcModuleName,
                       pLevelTag[ucLevel],
                       pcFileName, (unsigned int)uiLine);
    }
}

/**
 *   @Description: LOG命令行回调函数
 *   @param : pArg - 命令行参数
 *   @return: NA
 */
static void LOG_CLICallBack(char *pArg)
{
    char *pcNextArg = NULL;
    char acCLIKey[CLI_CMD_NAME_MAX_LEN];

    pcNextArg = CLI_KeyNext(pArg, acCLIKey);
    if (0 == strcmp(acCLIKey, "level"))
    {
        uint8_t ucLevel;
        
        pcNextArg = CLI_KeyNext(pcNextArg, acCLIKey);
        ucLevel = (uint8_t)atoi(acCLIKey);
        if (ucLevel < LOG_LEVEL_INVALID)
        {
            s_stLogContext.ucLevel = ucLevel;
        }
    }
}

static void LOG_DbgPrint(const LOGBUF_NODE_S *pstLogBuf)
{
    if (pstLogBuf)
    {
        printf("%s", pstLogBuf->acBuffer);
    }
}

#else   // LOG _DISABLE

void LOG_Init(uint8_t ucLevel)
{
}

void LOG_Output(char *pcModuleName, uint8_t ucLevel, uint32_t uiKey, char *pcFileName, uint32_t uiLine, char *pcFormatMsg, ...)
{
}

void LOG_HexDumpOutput(char *pcModuleName, uint32_t uiKey, char *pcTitle, const uint8_t *pucMsg, uint32_t uiMsgLen, char *pcFileName, uint32_t uiLine)
{
}

int32_t LOG_OutputRegister(logoutput_callback_t pfLogOutput)
{
}

int32_t LOG_OutputUnregister(logoutput_callback_t pfLogOutput)
{
}

void LOG_Flush(uint32_t uiLogNum)
{
}

#endif // SAL_LOG == _ENABLE

/************************************** The End Of File **************************************/

